package org.budo.dubbo.protocol.http.sign;

import java.util.Arrays;

import org.budo.dubbo.protocol.http.Consts;
import org.budo.dubbo.protocol.http.exception.BudoRuntimeException;
import org.budo.dubbo.protocol.http.exception.remoting.SignCheckException;
import org.budo.dubbo.protocol.http.exception.remoting.SignCheckException.Fail;
import org.budo.dubbo.protocol.http.invocation.BudoDubboRpcInvocation;
import org.budo.dubbo.rpc.util.InvocationUtil;
import org.budo.support.java.net.util.JavaNetUtil;
import org.budo.support.lang.util.ProcessUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.mvcs.Mvcs;
import org.budo.time.Time;
import org.springframework.util.DigestUtils;

import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.rpc.Invocation;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Slf4j
public abstract class AbstractSignCheckService implements SignCheckService {
    /**
     * 发起调用和被调用时均会使用到
     */
    public abstract String getAppSecret(String appKey);

    /**
     * 发起调用时使用到
     */
    public abstract String getAppKey(Invocation invocation);

    @Override
    public SignEntity sign(Invocation invocation) {
        String consumerAppKey = this.getAppKey(invocation);
        if (StringUtil.isNullOrEmpty(consumerAppKey) || consumerAppKey.startsWith("${")) {
            throw new IllegalArgumentException("#37 consumerSideAppKey=" + consumerAppKey + ", this=" + this //
                    + ", ipAddresses=" + JavaNetUtil.getIpAddresses() + ", processName=" + ProcessUtil.getCurrentProcessName() + ", processId=" + ProcessUtil.getCurrentProcessId() //
                    + ", requestURI=" + Mvcs.getRequestURI() + ", headers=" + Mvcs.getRequestHeaderMap() + ", requestBody=" + Mvcs.getRequestBody());
        }

        String consumerAppSecret = this.getAppSecret(consumerAppKey);
        if (StringUtil.isNullOrEmpty(consumerAppSecret) || consumerAppSecret.startsWith("${")) {
            throw new IllegalArgumentException("#37 consumerSideAppSecret=" + consumerAppSecret + ", this=" + this //
                    + ", ipAddresses=" + JavaNetUtil.getIpAddresses() + ", processName=" + ProcessUtil.getCurrentProcessName() + ", processId=" + ProcessUtil.getCurrentProcessId() //
                    + ", requestURI=" + Mvcs.getRequestURI() + ", headers=" + Mvcs.getRequestHeaderMap() + ", requestBody=" + Mvcs.getRequestBody());
        }

        String requestBody = ((BudoDubboRpcInvocation) invocation).getRequestBody();
        String consumerSideTime = Time.now().toMilliSeconds().toString();

        String sign_v2 = this.sign_v2(requestBody, consumerSideTime, consumerAppSecret);
        SignEntity signEntity = new SignEntity(sign_v2, consumerAppKey, consumerSideTime);
        if (log.isDebugEnabled()) {
            log.debug("#44 signEntity=" + signEntity + ", requestBody=" + requestBody + ", consumerSideAppSecret=" + consumerAppSecret);
        }

        return signEntity;
    }

    @Override
    public Boolean signCheck(Invocation invocation) {
        Boolean taojinSignCheck = this.taojinSignCheck(invocation);

        String consumerAppKey = this.readSignCheckAttachment(invocation, new String[] { Consts.HeaderKey.AppKey_2, Consts.HeaderKey.AppKey }, Fail.APP_KEY_EMPTY);

        if ((StringUtil.isNullOrEmpty(consumerAppKey) || consumerAppKey.startsWith("${") || consumerAppKey.trim().equals("null")) //
                && !taojinSignCheck) { // 特殊处理
            throw new IllegalArgumentException("#61 consumerSideAppKey=" + consumerAppKey + ", this=" + this //
                    + ", ipAddresses=" + JavaNetUtil.getIpAddresses() + ", processName=" + ProcessUtil.getCurrentProcessName() + ", processId=" + ProcessUtil.getCurrentProcessId() //
                    + ", requestURI=" + Mvcs.getRequestURI() + ", headers=" + Mvcs.getRequestHeaderMap() + ", requestBody=" + Mvcs.getRequestBody());
        }

        String providerSideAppSecret = this.getAppSecret(consumerAppKey);
        if ((StringUtil.isNullOrEmpty(providerSideAppSecret) || providerSideAppSecret.startsWith("${") || providerSideAppSecret.trim().equals("null")) //
                && !taojinSignCheck) { // 特殊处理
            throw new IllegalArgumentException("#66 providerSideAppSecret=" + providerSideAppSecret + ", consumerAppKey=" + consumerAppKey + ", this=" + this //
                    + ", ipAddresses=" + JavaNetUtil.getIpAddresses() + ", processName=" + ProcessUtil.getCurrentProcessName() + ", processId=" + ProcessUtil.getCurrentProcessId() //
                    + ", requestURI=" + Mvcs.getRequestURI() + ", headers=" + Mvcs.getRequestHeaderMap() + ", requestBody=" + Mvcs.getRequestBody());
        }

        String consumerSideSign = this.readSignCheckAttachment(invocation, new String[] { Consts.HeaderKey.Sign_2, Consts.HeaderKey.Sign }, Fail.SIGN_EMPTY);

        String consumerSideTime = this.readSignCheckAttachment(invocation, new String[] { Consts.HeaderKey.Time_2, Consts.HeaderKey.Time }, Fail.TIME_EMPTY); // 顺序靠后，优化错误提示信息

        String requestBody = ((BudoDubboRpcInvocation) invocation).getRequestBody();

        // 最新版本sign‘验证
        String providerSideSign_v2 = this.sign_v2(requestBody, consumerSideTime, providerSideAppSecret);
        if (StringUtil.equalsIgnoreCase(providerSideSign_v2, consumerSideSign)) {
            return true;
        }

        if (taojinSignCheck) { // 特殊处理
            String message = "#91 providerSideSign_v2=" + providerSideSign_v2 + ", consumerSideSign=" + consumerSideSign + ", consumerSideTime=" + consumerSideTime //
                    + ", providerSideAppSecret=" + providerSideAppSecret + ", requestBody=" + StringUtil.cutLeft(requestBody, 1024) + ", this=" + this//
                    + ", ipAddresses=" + JavaNetUtil.getIpAddresses() + ", processName=" + ProcessUtil.getCurrentProcessName() + ", processId=" + ProcessUtil.getCurrentProcessId();
            log.error(message);
            return true;
        }

        String message = "#98 providerSideSign_v2=" + providerSideSign_v2 + ", consumerSideSign=" + consumerSideSign + ", consumerSideTime=" + consumerSideTime //
                + ", providerSideAppSecret=" + providerSideAppSecret + ", requestBody=" + StringUtil.cutLeft(requestBody, 1024) + ", this=" + this//
                + ", ipAddresses=" + JavaNetUtil.getIpAddresses() + ", processName=" + ProcessUtil.getCurrentProcessName() + ", processId=" + ProcessUtil.getCurrentProcessId();
        log.error(message);
        return false;
    }

    protected String readSignCheckAttachment(Invocation invocation, String[] keys, Fail fail) {
        String value = null;
        for (String key : keys) {
            value = invocation.getAttachment(key);
            if (StringUtils.isNotEmpty(value)) {
                break;
            }
        }

        if (StringUtils.isEmpty(value) //
                && !this.taojinSignCheck(invocation)) { // 特殊处理
            String message = "#106 this = " + this + ",  keys = " + Arrays.toString(keys) + " in attachment required, invocation=" + InvocationUtil.toString(invocation) //
                    + ", serverName=" + Mvcs.getServerName() + ", uri=" + Mvcs.getRequestURI() + ", headers=" + Mvcs.getRequestHeaderMap() //
                    + ", ipAddresses=" + JavaNetUtil.getIpAddresses() + ", processName=" + ProcessUtil.getCurrentProcessName() + ", processId=" + ProcessUtil.getCurrentProcessId();
            log.error(message); // log error

            throw new SignCheckException(message, fail);
        }

        return value;
    }

    /**
     * 特殊处理; 泛化调用时 取不到注解
     */
    private Boolean taojinSignCheck(Invocation invocation) {
        String attachment = invocation.getAttachment("SessionId");
        if (null != attachment && attachment.startsWith("taojin-marking-web-launcher_")) {
            log.warn("#120 taojinSignCheck, invocation=" + invocation);
            return true;
        }

        return false;
    }

    /**
     * 按照key的字母顺序来
     */
    protected String sign_v2(String requestBody, String time, String appSecret) {
        String line = "appSecret=" + appSecret + "&requestBody=" + requestBody + "&time=" + time;

        return this.md5(line);
    }

    protected String md5(String toSignString) {
        byte[] toSignBytes = toSignString.getBytes();

        try {
            return DigestUtils.md5DigestAsHex(toSignBytes);
        } catch (Throwable e) {
            String message = "#172 md5 error, toSignString=" + toSignString + ", e=" + e;
            throw new BudoRuntimeException(message, e);
        }
    }
}
